﻿//SciChartElement.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Xaml
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Windows;
	using System.Windows.Media;

	using Abt.Controls.SciChart;

	using Ecng.Common;
	using Ecng.Xaml;
	using Ecng.Collections;

	using StockSharp.Algo.Candles;
	using StockSharp.Algo.Indicators;
	using StockSharp.BusinessEntities;

	partial class SciChartPaneViewModel
    {
        private SciChartElement CreateElement(IChartElement fromElem)
        {
	        if (fromElem == null)
		        throw new ArgumentNullException("fromElem");

	        var celem = fromElem as ChartCandleElement;
            if (celem != null) return new SciChartCandles(this, celem);

            var ielem = fromElem as ChartIndicatorElement;
            if (ielem != null) return new SciChartIndicator(this, ielem);

            var oelem = fromElem as ChartOrderElement;
            if (oelem != null) return new SciChartOrders(this, oelem);

            var telem = fromElem as ChartTradeElement;
            if (telem != null) return new SciChartTrades(this, telem);

            throw new ArgumentException("Неизвестный элемент графика {0}.".Put(fromElem));
        }

        private abstract class SciChartElement
        {
			protected SciChartPaneViewModel Pane { get; private set; }
			protected IChartElement Elem { get; private set; }

            protected SciChartElement(SciChartPaneViewModel pane, IChartElement fromElem)
            {
	            if (pane == null)
		            throw new ArgumentNullException("pane");

	            if (fromElem == null)
		            throw new ArgumentNullException("fromElem");

	            Pane = pane;
                Elem = fromElem;
            }

            public abstract void OnRemoving();
            public abstract void Reset();
            public abstract void ProcessValue(DateTime time, object value);
        }

        /// <summary>
        /// Элемент графика - серия свечек
        /// </summary>
        private sealed class SciChartCandles : SciChartElement
        {
            private readonly ChartSeriesViewModel _vm;
            private readonly OhlcDataSeries<DateTime, double> _ohlcSeries;

			//private ChartCandleElement CandlesElement
			//{
			//	get { return (ChartCandleElement) Elem; }
			//}

            public SciChartCandles(SciChartPaneViewModel pane, ChartCandleElement fromElem)
				: base(pane, fromElem)
            {
                _ohlcSeries = new OhlcDataSeries<DateTime, double>();

	            var renderable = new FastCandlestickRenderableSeries();
				renderable.UpWickColor = renderable.UpBodyColor = fromElem.ColorPriceUp;
				renderable.DownWickColor = renderable.DownBodyColor = fromElem.ColorPriceDown;

                _vm = new ChartSeriesViewModel(_ohlcSeries, renderable);
                Pane.ChartSeriesViewModels.Add(_vm);
            }

            public override void OnRemoving()
            {
                Reset();
                Pane.ChartSeriesViewModels.Remove(_vm);
            }

            public override void Reset()
            {
                _ohlcSeries.Clear();
            }

            public override void ProcessValue(DateTime time, object value)
            {
                var needToScroll = false;

                try
                {
                    var candle = value as Candle;
                    if (candle == null)
                    {
						//XMax нельзя использовать, т.к. при любом вызове Append или Update
						//XMax автоматически становится равно переданному значению, 
						//т.о. нет возможности корректно менять любое значение, кроме как последнее
						if (time > _ohlcSeries.XValues.LastOrDefault())
                        {
                            needToScroll = Pane._chart.IsAutoScroll &&
                                           Pane.ParentViewModel.XVisibleRange.Max >= _ohlcSeries.Count - 1;
                            _ohlcSeries.Append(time, double.NaN, double.NaN, double.NaN, double.NaN);
                        }
                        else
                        {
                            _ohlcSeries.Update(time, double.NaN, double.NaN, double.NaN, double.NaN);
                        }

                        return;
                    }

                    if (candle.OpenTime != time)
						throw new ArgumentException("candle.OpenTime != time", "time");

					if (time > _ohlcSeries.XValues.LastOrDefault())
                    {
                        needToScroll = Pane._chart.IsAutoScroll &&
                                       Pane.ParentViewModel.XVisibleRange.Max >= _ohlcSeries.Count - 1 &&
									   Pane.ParentViewModel.MinimumRange < _ohlcSeries.Count;

                        _ohlcSeries.Append(time, (double) candle.OpenPrice, (double) candle.HighPrice,
                                           (double) candle.LowPrice, (double) candle.ClosePrice);
                    }
                    else
                    {
                        _ohlcSeries.Update(time, (double) candle.OpenPrice, (double) candle.HighPrice,
                                           (double) candle.LowPrice, (double) candle.ClosePrice);
                    }
                }
                finally
                {
					if (needToScroll)
					{
						Pane._chart.Dispatcher.GuiAsync(() =>
						{
							int newFrom = Pane.ParentViewModel.XVisibleRange.Min + 1;
							if (newFrom < 0) newFrom = 0;

							int newTo = Pane.ParentViewModel.XVisibleRange.Max + 1;
							//if(newTo > _ohlcSeries.Count + 1) newTo = _ohlcSeries.Count + 1;
							if (newTo < newFrom) newTo = newFrom;
							Pane.ParentViewModel.XVisibleRange = new IndexRange(newFrom, newTo);
						});
					}
                }
            }
        }

        /// <summary>
        /// Элемент графика - индикатор
        /// </summary>
        private sealed class SciChartIndicator : SciChartElement
        {
            private readonly Dictionary<IIndicator, ChartSeriesViewModel> _vms =
                new Dictionary<IIndicator, ChartSeriesViewModel>();

            private ChartIndicatorElement IndicatorElement
            {
                get { return (ChartIndicatorElement) Elem; }
            }

	        public SciChartIndicator(SciChartPaneViewModel pane, ChartIndicatorElement fromElem)
		        : base(pane, fromElem)
	        {
		        foreach (var ind in GetSimpleIndicators(fromElem.Indicator))
		        {
			        var renderable = new FastLineRenderableSeries
			        {
				        SeriesColor = IndicatorElement.Color,
				        StrokeThickness = 2
			        };

			        var vm = new ChartSeriesViewModel(new XyDataSeries<DateTime, double>(), renderable);
			        _vms[ind] = vm;
			        Pane.ChartSeriesViewModels.Add(vm);
		        }
	        }

	        public override void OnRemoving()
            {
                Reset();

                foreach (var vm in _vms.Values)
                    Pane.ChartSeriesViewModels.Remove(vm);
            }

            public override void Reset()
            {
                foreach (var vm in _vms.Values)
                    ((XyDataSeries<DateTime, double>) vm.DataSeries).Clear();
            }

            public override void ProcessValue(DateTime time, object value)
            {
                var cvalue = value as ChartIndicatorValue;

                if (cvalue == null || !cvalue.IsFormed)
                {
					// add/update empty value
					foreach (var vm in _vms.Values)
					{
						var series = ((XyDataSeries<DateTime, double>)vm.DataSeries);

						if (time > series.XValues.LastOrDefault())
							series.Append(time, double.NaN);
						else
							series.Update(time, double.NaN);
					}

                    return;
                }

                var values = GetSimpleValues(cvalue.Indicator, cvalue.Value);
                foreach (var val in values)
                {
					var vm = _vms.TryGetValue(val.Key);

					if (vm == null)
                        throw new ArgumentException("no such indicator", "value");

                    var series = ((XyDataSeries<DateTime, double>) vm.DataSeries);

					if (time > series.XValues.LastOrDefault())
                        series.Append(time, (double) val.Value.GetValue<decimal>());
                    else
                        series.Update(time, (double) val.Value.GetValue<decimal>());
                }
            }

            private static Dictionary<IIndicator, IIndicatorValue> GetSimpleValues(IIndicator indicator,
                                                                                   IIndicatorValue value,
                                                                                   Dictionary
                                                                                   <IIndicator, IIndicatorValue>
                                                                                   dict = null)
            {
                if (dict == null) dict = new Dictionary<IIndicator, IIndicatorValue>();
                if (value == null) return dict;

                var complex = value as ComplexIndicatorValue;
                if (complex != null)
                {
                    foreach (var inner in complex.InnerValues)
                        GetSimpleValues(inner.Key, inner.Value, dict);
                }
                else
                {
                    dict[indicator] = value;
                }

                return dict;
            }

            private static List<IIndicator> GetSimpleIndicators(IIndicator ind, List<IIndicator> list = null)
            {
                if (list == null) list = new List<IIndicator>();

                var complex = ind as IComplexIndicator;
                if (complex == null)
                {
                    list.Add(ind);
                    return list;
                }

                foreach (var i in complex.InnerIndicators)
                    GetSimpleIndicators(i, list);

                return list;
            }
        }

        /// <summary>
        ///     Элемент графика - трейды
        /// </summary>
        private sealed class SciChartTrades : SciChartElement
        {
			//private ChartTradeElement TradeElement
			//{
			//	get { return (ChartTradeElement) _elem; }
			//}

            private readonly Dictionary<MyTrade, SciChartTradeAnnotationBase> _annotations =
                new Dictionary<MyTrade, SciChartTradeAnnotationBase>();

            public SciChartTrades(SciChartPaneViewModel pane, ChartTradeElement fromElem)
				: base(pane, fromElem)
            {
            }

            public override void OnRemoving()
            {
                Reset();
            }

            public override void Reset()
            {
                foreach (var a in _annotations.Values)
                    Pane.Annotations.Remove(a);

                _annotations.Clear();
            }

            public override void ProcessValue(DateTime time, object value)
            {
				var mytrade = value as MyTrade;

				if (mytrade == null)
					return;

                Pane._chart.Dispatcher.GuiAsync(() =>
                {
                    var a = mytrade.Trade.OrderDirection == OrderDirections.Buy
                                                        ? (SciChartTradeAnnotationBase)
                                                            new SciChartBuyMarkerAnnotation(mytrade)
                                                        : new SciChartSellMarkerAnnotation(mytrade);

                    _annotations[mytrade] = a;
                    a.X1 = time;
                    a.Y1 = (double) mytrade.Trade.Price;

                    Pane.Annotations.Add(a);
                });
            }
        }

        /// <summary>
        ///     Элемент графика - ордера
        /// </summary>
        private sealed class SciChartOrders : SciChartElement
        {
            private ChartOrderElement OrderElement
            {
                get { return (ChartOrderElement) Elem; }
            }

            private readonly Dictionary<Order, HorizontalLineAnnotation> _annotations =
                new Dictionary<Order, HorizontalLineAnnotation>();

            public SciChartOrders(SciChartPaneViewModel pane, ChartOrderElement fromElem)
				: base(pane, fromElem)
            {
            }

            public override void OnRemoving()
            {
                Reset();
            }

            public override void Reset()
            {
                foreach (var a in _annotations.Values)
                    Pane.Annotations.Remove(a);

                _annotations.Clear();
            }

            public override void ProcessValue(DateTime time, object value)
            {
                Pane._chart.Dispatcher.GuiAsync(() =>
                {
                    var order = value as Order;
                    if (order == null) return;

	                var ann = _annotations.TryGetValue(order);

                    if (ann != null)
                    {
                        if (time == DateTime.MinValue)
                        {
                            _annotations.Remove(order);
                            Pane.Annotations.Remove(ann);
                        }

                        return;
                    }

	                ann = new HorizontalLineAnnotation
	                {
		                CoordinateMode = AnnotationCoordinateMode.RelativeX,
		                Stroke =
			                new SolidColorBrush(order.Direction == OrderDirections.Buy
				                                    ? OrderElement.BuyColor
				                                    : OrderElement.SellColor),
		                HorizontalAlignment = HorizontalAlignment.Right,
		                FontSize = 10,
		                LabelPlacement = LabelPlacement.Left,
		                LabelValue = "{0} {1} @ {2}".Put(order.Direction, order.Volume, order.Price),
		                ShowLabel = true,
		                Y1 = (double)order.Price,
		                X1 = 0.8
	                };

                    _annotations[order] = ann;

                    Pane.Annotations.Add(ann);
                });
            }
        }
    }
}
